package com.mindata.blockchain.block.RLP;

import com.mindata.blockchain.block.BlockHeader;
import com.mindata.blockchain.common.util.ByteArrayWrapper;
import com.mindata.blockchain.common.util.ByteUtil;
import com.mindata.blockchain.common.util.RLP;
import com.mindata.blockchain.common.util.RLPList;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by Ming on 2018/8/10.
 */
public class BlockHeaderB {

    private static final byte[] EMPTY_BYTE_ARRAY = new byte[]{};

    private byte[] version;

    private byte[] hashPreviousBlock;

    private byte[] hashMerkleRoot;

    private byte[] publicKey;

    private byte[] number;

    private byte[] timeStamp;

    private byte[] nonce;

    private byte[][] hashList;

    protected byte[] rlpEncoded;

    private boolean parsed = false;

    public BlockHeaderB() {
    }

    public BlockHeaderB(BlockHeader blockHeader) {
        this.version = ByteUtil.intToBytes(blockHeader.getVersion());
        this.hashPreviousBlock = blockHeader.getHashPreviousBlock() != null ? blockHeader.getHashPreviousBlock().getBytes() : EMPTY_BYTE_ARRAY;
        this.hashMerkleRoot = blockHeader.getHashMerkleRoot() != null ? blockHeader.getHashMerkleRoot().getBytes() : EMPTY_BYTE_ARRAY;
        this.publicKey = blockHeader.getPublicKey() != null ? blockHeader.getPublicKey().getBytes() : EMPTY_BYTE_ARRAY;
        this.number = ByteUtil.intToBytes(blockHeader.getNumber());
        this.timeStamp = ByteUtil.longToBytes(blockHeader.getTimeStamp());
        this.nonce = ByteUtil.longToBytes(blockHeader.getNonce());
        if (blockHeader.getHashList() != null) {
            List<String> hashList = blockHeader.getHashList();
            this.hashList = new byte[hashList.size()][];
            for (int i = 0; i < hashList.size(); i++)
                this.hashList[i] = hashList.get(i).getBytes();
        }
    }

    public BlockHeaderB(byte[] rawData) {
        this.rlpEncoded = rawData;
    }

    public synchronized void parseRLP() {
        if (parsed || ByteUtil.isNullOrZeroArray(rlpEncoded))
            return;

        RLPList params = (RLPList) RLP.decode2(rlpEncoded).get(0);
        this.version = params.get(0).getRLPData();
        this.hashPreviousBlock = params.get(1).getRLPData();
        this.hashMerkleRoot = params.get(2).getRLPData();
        this.publicKey = params.get(3).getRLPData();
        this.number = params.get(4).getRLPData();
        this.timeStamp = params.get(5).getRLPData();
        this.nonce = params.get(6).getRLPData();
        this.hashList = new byte[((RLPList) params.get(7)).size()][];
        for (int i = 0; i < hashList.length; i++) {
            this.hashList[i] = ((RLPList) params.get(7)).get(i).getRLPData();
        }
        this.parsed = true;
    }


    public byte[] getVersion() {
        parseRLP();
        return version;
    }

    public void setVersion(byte[] version) {
        this.version = version;
    }

    public byte[] getHashPreviousBlock() {
        parseRLP();
        return hashPreviousBlock;
    }

    public void setHashPreviousBlock(byte[] hashPreviousBlock) {
        this.hashPreviousBlock = hashPreviousBlock;
    }

    public byte[] getHashMerkleRoot() {
        parseRLP();
        return hashMerkleRoot;
    }

    public void setHashMerkleRoot(byte[] hashMerkleRoot) {
        this.hashMerkleRoot = hashMerkleRoot;
    }

    public byte[] getPublicKey() {
        parseRLP();
        return publicKey;
    }

    public void setPublicKey(byte[] publicKey) {
        this.publicKey = publicKey;
        rlpEncoded = null;
    }

    public byte[] getNumber() {
        parseRLP();
        return number;
    }

    public void setNumber(byte[] number) {
        this.number = number;
    }

    public byte[] getTimeStamp() {
        parseRLP();
        return timeStamp;
    }

    public void setTimeStamp(byte[] timeStamp) {
        this.timeStamp = timeStamp;
    }

    public byte[] getNonce() {
        parseRLP();
        return nonce;
    }

    public void setNonce(byte[] nonce) {
        this.nonce = nonce;
    }

    public byte[][] getHashList() {
        parseRLP();
        return hashList;
    }

    public void setHashList(byte[][] hashList) {
        this.hashList = hashList;
    }

    public byte[] encode() {
        if (rlpEncoded == null) {
            byte[] version = encode(this.version);
            byte[] hashPreviousBlock = encode(this.hashPreviousBlock);
            byte[] hashMerkleRoot = encode(this.hashMerkleRoot);
            byte[] publicKey = encode(this.publicKey);
            byte[] number = encode(this.number);
            byte[] timeStamp = encode(this.timeStamp);
            byte[] nonce = encode(this.nonce);
                Set<ByteArrayWrapper> hashL=new HashSet<>();
            for(byte[] bytes:this.hashList)
                hashL.add(new ByteArrayWrapper(bytes));
            byte[] hashList = RLP.encodeSet(hashL);
            this.rlpEncoded = RLP.encodeList(version, hashPreviousBlock, hashMerkleRoot, publicKey, number, timeStamp, nonce, hashList);
        }
        return rlpEncoded;
    }

    public byte[] encode(byte[] var) {
        return RLP.encodeElement(var);
    }

    public BlockHeader convertToBlockHeader() {
        parseRLP();
        BlockHeader blockHeader = new BlockHeader();

        if (!ByteUtil.isNullOrZeroArray(getVersion()))
            blockHeader.setVersion(ByteUtil.byteArrayToInt(getVersion()));

        if (!ByteUtil.isNullOrZeroArray(getHashPreviousBlock()))
            blockHeader.setHashPreviousBlock(new String(getHashPreviousBlock()));

        if (!ByteUtil.isNullOrZeroArray(getHashMerkleRoot()))
            blockHeader.setHashMerkleRoot(new String(getHashMerkleRoot()));

        if (!ByteUtil.isNullOrZeroArray(getPublicKey()))
            blockHeader.setPublicKey(new String(getPublicKey()));

        if (!ByteUtil.isNullOrZeroArray(getNumber()))
            blockHeader.setNumber(ByteUtil.byteArrayToInt(getNumber()));

        if (!ByteUtil.isNullOrZeroArray(getTimeStamp()))
            blockHeader.setTimeStamp(ByteUtil.byteArrayToLong(getTimeStamp()));

        if (!ByteUtil.isNullOrZeroArray(getNonce()))
            blockHeader.setNonce(ByteUtil.byteArrayToLong(getNonce()));

        List<String> hashList = new ArrayList<>();
        for (int i = 0; i < getHashList().length; i++) {
            if (!ByteUtil.isNullOrZeroArray(getHashList()[i]))
                hashList.add(new String(getHashList()[i]));
        }
        blockHeader.setHashList(hashList);

        return blockHeader;
    }
}
